import java.awt.Point;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;


public class Bullet {
	private static int counter;
	private int id;
	private Client owner;
	
	private String type;
	private float spawnX;
	private float spawnY;
	private float positionX;
	private float positionY;
	private float directionX;
	private float directionY;
	private float speed;
	private int damage;
	private Rectangle2D bounds = new Rectangle2D.Float();
	private boolean isAlive = true;
	
	private long spawnTime;
	
	public Bullet(String type, float spawnX, float spawnY, float directionX, float directionY, float speed){
		this.id = counter;
		counter++;
		
		this.type = type;
		this.spawnX = spawnX;
		this.spawnY = spawnY;
		this.positionX = spawnX;
		this.positionY = spawnY;
		this.directionX = directionX;
		this.directionY = directionY;
		this.speed = speed;
		this.damage = 20;
		updateBounds();
		
		this.spawnTime = System.nanoTime();
	}
	
	public void update()
	{
		if (isAlive)
		{
			float totalTimeElapsedInSeconds  = (System.nanoTime() - this.spawnTime) / 1000000000f; // nanoseconds to seconds
			
			this.positionX = this.spawnX + ((directionX * speed) * totalTimeElapsedInSeconds);
			this.positionY = this.spawnY + ((directionY * speed) * totalTimeElapsedInSeconds);
			updateBounds();
			
			//if bullet collides with a player
			Client collisionPlayer = bulletCollidesWithPlayer();
			if(collisionPlayer != null){ //null means no collision was found.
				//Damage player the bullet collided with.
				collisionPlayer.getPlayerData().damagePlayer(this.damage, this.owner);
				int collisionPlayerId = collisionPlayer.getClientId();
				//Send tcp signal to all players
				int xPos = (int)this.positionX;
				int yPos = (int)this.positionY;
				ConnectionHandler.getInstance().sendToAllClients("31_" + this.id + "_" + collisionPlayerId + "_" + this.damage + "_" + xPos + "_" + yPos);
				//Remove bullet
				this.owner.getPlayerData().removeBullet(this);
			}
			else handleCollisionWithTiles(); //Check if bullet collides with a tile.
			//System.out.println("X=" + positionX + "  Y=" + positionY);
			
			
		}
		//REMOVE WHEN BULLET IS OUT OF SIGHT/LEVEL
		//If bullet is still alive after the collision checks (meaning no collision was detected)
		if(isAlive){
			//check if bullet has left the level
			int levelMargin = 200; //margin around the level from which to remove the bullet.
			int levelWidth = Level.getInstance().getWidthInTiles();
			int levelHeight = Level.getInstance().getHeightInTiles();
			if(this.positionX < 0 - levelMargin //bullet left on left side of level.
					|| this.positionX > (levelWidth * Settings.tileSize) + levelMargin //bullet left on right side of level.
					|| this.positionY < 0 - levelMargin //bullet left on top side of level.
					|| this.positionY > (levelHeight * Settings.tileSize) + levelMargin) //bullet left on bottom side of level.
			{
				isAlive = false;
				this.owner.getPlayerData().removeBullet(this);
			}
		}
	}
	
	/**
	 * Checks for collision between this bullet and any player (except the owner).
	 * @return Returns -1 if no collision is detected. Otherwise returns the id of the player with whom the bullet collided.
	 */
	private Client bulletCollidesWithPlayer(){
		ArrayList<Client> players = ConnectionHandler.getInstance().getActiveClients();
		for(int i = 0; i < players.size(); i++){
			Client player = players.get(i);
			//Only collide if the player is not the owner of this bullet and if the player is alive
			if(player.getClientId() != this.getOwner().getClientId() && player.getPlayerData().isAlive()){
				//If the bullet bounds collide with the player bounds
				if(this.bounds.intersects(player.getPlayerData().getBounds())){
					isAlive = false;
					return player;
				}
			}
		}
		return null;
	}
	
	/**
	 * Checks for collision between this bullet and a tile.
	 */
	private void handleCollisionWithTiles(){
		//Calculate the position of every border of this bullet.
		//Check if the index it is on, contains a tile (> 0)
		//if so, collision is detected. Notify clients and return;
		int minX = (int)(this.bounds.getMinX() / Settings.tileSize);
		//System.out.println(this.bounds.getMinX());
		int minY = (int)(this.bounds.getMinY() / Settings.tileSize);
		int maxX = (int)(this.bounds.getMaxX() / Settings.tileSize);
		int maxY = (int)(this.bounds.getMaxY() / Settings.tileSize);
		
		int topLeftTile = Level.getInstance().getTile(minX, minY);
		int topRightTile = Level.getInstance().getTile(maxX, minY);
		int bottomLeftTile = Level.getInstance().getTile(minX, maxY);
		int bottomRightTile = Level.getInstance().getTile(maxX, maxY);
		
		int[] corners = {topLeftTile, topRightTile, bottomLeftTile, bottomRightTile};
		for(int i = 0; i < corners.length; i++){
			if(corners[i] > 0 && corners[i] != 100) //no air tile and no spawn tile
			{
				//BULLET COLLISION WITH A TILE
				int xPos = (int)this.positionX;
				int yPos = (int)this.positionY;
				//Send tcp signal to all players
				ConnectionHandler.getInstance().sendToAllClients("32_" + this.id + "_" + xPos + "_" + yPos);
				isAlive = false;
				//Remove bullet
				this.owner.getPlayerData().removeBullet(this);
				return;
			}
		}
		
	}
	
	private void updateBounds(){
		float sizefactor = 0.2f;
		this.bounds = new Rectangle2D.Float(positionX - ((Constants.fireballWidth/2)*sizefactor)
				, positionY - ((Constants.fireballHeight/2)*sizefactor)
				, Constants.fireballWidth * sizefactor, Constants.fireballHeight * sizefactor);
	}
	
	public float getPingAdaptedSpawnPositionX(Client client)
	{
		float x = this.spawnX + ((this.directionX * this.speed) * (client.getPing() / 1000f));
		return x;
	}
	
	public float getPingAdaptedSpawnPositionY(Client client)
	{
		float y = this.spawnY + ((this.directionY * this.speed) * (client.getPing() / 1000f));
		return y;
	}
	
	public void setOwner(Client client){
		this.owner = client;
	}
	public Client getOwner(){
		return this.owner;
	}
	
	public String getType(){
		return this.type;
	}
	
	public int getId(){
		return this.id;
	}
	
	public boolean isAlive(){
		return this.isAlive;
	}
}
